package cool.doudou.doudada.wechat.rest.service;

import com.alibaba.fastjson2.JSONObject;
import cool.doudou.doudada.wechat.core.Constant;
import cool.doudou.doudada.wechat.core.api.WechatApi;
import cool.doudou.doudada.wechat.core.entity.Credential;
import cool.doudou.doudada.wechat.core.entity.JsSignature;
import cool.doudou.doudada.wechat.core.entity.xml.ReceiveMsg;
import cool.doudou.doudada.wechat.core.factory.MethodFactory;
import cool.doudou.doudada.wechat.core.factory.TokenMapFactory;
import cool.doudou.doudada.wechat.core.helper.ReplyMsgHelper;
import cool.doudou.doudada.wechat.core.method.NotifyMethod;
import cool.doudou.doudada.wechat.core.method.OAuth2Method;
import cool.doudou.doudada.wechat.core.util.SignUtil;
import cool.doudou.doudada.wechat.core.util.XmlUtil;
import cool.doudou.doudada.wechat.properties.WechatProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.ModelAndView;

import java.lang.reflect.Method;
import java.util.UUID;

/**
 * WechatService
 *
 * @author jiangcs
 * @since 2022/07/04
 */
@Slf4j
public class WechatService {
    private WechatProperties wechatProperties;
    private ReplyMsgHelper replyMsgHelper;

    /**
     * 验证消息的确来自微信服务器
     *
     * @param signature 微信加密签名
     * @param timestamp 时间戳
     * @param nonce     随机数
     * @param echostr   随机字符串
     * @return 空字符串-验证异常；echostr-验证成功
     */
    public String check(String signature, String timestamp, String nonce, String echostr) {
        // 判断参数是否正常
        if (ObjectUtils.isEmpty(signature) || ObjectUtils.isEmpty(timestamp)
                || ObjectUtils.isEmpty(nonce) || ObjectUtils.isEmpty(echostr)) {
            log.error("微信服务器验证失败：参数异常，拒绝访问.");
            return "";
        }

        try {
            if (wechatProperties.getCredentials().isEmpty()) {
                log.error("微信服务器验证失败：参数配置错误.");
                return "";
            }
            // 参数签名验证：成功返回echostr，则微信开发者接入成功
            for (Credential credential : wechatProperties.getCredentials()) {
                String signatureStr = SignUtil.server(credential.getToken(), timestamp, nonce);
                if (signature.equals(signatureStr)) {
                    log.info("微信服务器验证通过：appId[{}]", credential.getAppId());
                    return echostr;
                }
            }

            log.error("微信服务器验证失败：拒绝访问.");
            return "";
        } catch (Exception e) {
            log.error("微信服务器验证异常：拒绝访问.：", e);
            return "";
        }
    }

    /**
     * 接收普通消息、接收事件推送
     *
     * @param xml XML数据
     */
    public void notify(String xml) {
        log.info("notify：{}", xml);

        try {
            ReceiveMsg receiveMsg = (ReceiveMsg) XmlUtil.unMarshal(xml, ReceiveMsg.class);
            NotifyMethod notifyMethod = MethodFactory.getNotify();
            if (notifyMethod != null) {
                Object bean = notifyMethod.getBean();
                Method method = notifyMethod.getMethod();
                try {
                    method.setAccessible(true);
                    Boolean respFlag = (Boolean) method.invoke(bean, receiveMsg);
                    if (respFlag) {
                        // respFlag为true，需要回复默认ok
                        replyMsgHelper.ok("success");
                    }
                } catch (Exception e) {
                    log.error("bean[{}].method[{}] invoke exception: ", bean, method.getName(), e);
                }
                return;
            }
            log.warn("No NotifyMethod found");
        } catch (Exception e) {
            log.error("消息通知异常", e);
        }
    }

    /**
     * 网页授权
     *
     * @param code  换取access_token的票据
     * @param state 重定向后会带上 state 参数
     * @return 前端页面视图
     */
    public ModelAndView oauth2(String code, String state) {
        log.info("oauth2: {}, {}", code, state);

        ModelAndView modelAndView;
        try {
            // 重定向后会带上 state 参数，开发者可以填写a-zA-Z0-9的参数值，最多128字节
            // 多个参数以"_"分隔，第1个参数必须是appId
            String[] stateArray = state.split("_");
            if (stateArray.length == 0) {
                throw new RuntimeException("微信授权回掉参数state验证失败");
            }

            String appId = stateArray[0], secret = null;
            if (wechatProperties.getCredentials().isEmpty()) {
                log.error("微信服务器参数配置错误.");
                throw new RuntimeException("微信服务器参数配置错误");
            }
            for (Credential credential : wechatProperties.getCredentials()) {
                if (appId.equals(credential.getAppId())) {
                    secret = credential.getSecret();
                    break;
                }
            }
            if (secret == null) {
                throw new RuntimeException("微信授权密钥验证失败");
            }

            // 通过code换取网页授权access_token
            JSONObject resultObj = WechatApi.oauth2Token(appId, secret, code);
            if (resultObj == null) {
                throw new RuntimeException("微信授权验证失败");
            }

            // OPENID
            String openId = resultObj.getString("openid");
            if (ObjectUtils.isEmpty(openId)) {
                throw new RuntimeException("微信通讯标识获取失败");
            }

            // 获取用户信息
            JSONObject userInfo = WechatApi.getUserInfo(openId, resultObj.getString("access_token"));
            if (ObjectUtils.isEmpty(userInfo)) {
                throw new RuntimeException("微信用户信息获取失败");
            }
            String route = null;
            OAuth2Method oauth2Method = MethodFactory.getOAuth2();
            if (oauth2Method != null) {
                Object bean = oauth2Method.getBean();
                Method method = oauth2Method.getMethod();
                try {
                    method.setAccessible(true);
                    route = (String) method.invoke(bean, state, userInfo);
                } catch (Exception e) {
                    log.error("bean[{}].method[{}] invoke exception: ", bean, method.getName(), e);
                }
            }
            log.info("oauth2 invoke route: {}", route);

            if (route == null) {
                throw new RuntimeException("OAuth2Method not exists");
            }

            modelAndView = new ModelAndView("redirect:" + wechatProperties.getCallbackServerAddress() + "/#/" + route);
        } catch (Exception e) {
            log.error("微信授权oauth2异常：", e);

            // 返回错误页面
            modelAndView = new ModelAndView("redirect:" + wechatProperties.getCallbackServerAddress() + "/#/error");
        }
        return modelAndView;
    }

    /**
     * JS 签名
     *
     * @param appId 应用ID
     * @param url   调用 JS 接口页面的完整URL
     * @return JS-SDK配置信息
     */
    public JsSignature jsSign(String appId, String url) {
        String nonceStr = UUID.randomUUID().toString();
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        JsSignature jsSignature = new JsSignature();
        jsSignature.setSignature(SignUtil.js(TokenMapFactory.get(Constant.KEY_JS_API_TICKET + appId), nonceStr, timestamp, url));
        jsSignature.setNonceStr(nonceStr);
        jsSignature.setTimestamp(timestamp);
        return jsSignature;
    }

    /**
     * 全局唯一接口调用凭据
     *
     * @return AccessToken
     */
    public String accessToken() {
        wechatProperties.getCredentials()
                .forEach(credential -> WechatApi.accessToken(credential.getAppId(), credential.getSecret()));
        return "ok";
    }

    /**
     * JS-SDK调用签名票据
     *
     * @return jsApiTicket
     */
    public String jsApiTicket() {
        wechatProperties.getCredentials()
                .forEach(credential -> WechatApi.jsApiTicket(credential.getAppId()));
        return "ok";
    }

    /**
     * 获取菜单信息
     *
     * @param appId 应用Id
     * @return 菜单信息
     */
    public String getMenu(String appId) {
        JSONObject resultObj = WechatApi.getMenu(appId);
        if (resultObj != null) {
            return resultObj.toString();
        }
        return null;
    }

    /**
     * 创建菜单
     *
     * @param appId 应用Id
     * @param json  菜单信息JSON字符串
     * @return true-成功；false-失败
     */
    public boolean createMenu(String appId, String json) {
        JSONObject resultObj = WechatApi.createMenu(appId, json);
        if (resultObj != null) {
            int code = resultObj.getInteger("errcode");
            return Constant.MSG_CODE_ZERO == code;
        }
        return false;
    }

    /**
     * 删除菜单
     *
     * @param appId 应用Id
     * @return true-成功；false-失败
     */
    public boolean deleteMenu(String appId) {
        JSONObject resultObj = WechatApi.deleteMenu(appId);
        if (resultObj != null) {
            int code = resultObj.getInteger("errcode");
            return Constant.MSG_CODE_ZERO == code;
        }
        return false;
    }

    @Autowired
    public void setWechatProperties(WechatProperties wechatProperties) {
        this.wechatProperties = wechatProperties;
    }

    @Autowired
    public void setRespMsgHelper(ReplyMsgHelper replyMsgHelper) {
        this.replyMsgHelper = replyMsgHelper;
    }
}
